package online.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import online.filter.helper.DuplicateBodyResponse;
import online.filter.helper.DuplicateHeaderWrapper;
import online.filter.helper.InterceptResponseWrapper;
import online.listener.SessionMutexListener;

import org.apache.logging.log4j.LogManager;

/**
 * 単一仮想URLフィルター
 *
 * @author Tadashi Nakayama
 */
public final class SingleVirtualUrlFilter implements Filter {

	/** クラス名 */
	private static final String CLAZZ = SingleVirtualUrlFilter.class.getName();

	/** 仮想URLパス */
	private String path;

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		this.path = filterConfig.getInitParameter("servletPath");
	}

	/**
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
		return;
	}

	/**
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 * javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@Override
	public void doFilter(final ServletRequest svRequest, final ServletResponse svResponse,
			final FilterChain chain) throws IOException, ServletException {
		if (HttpServletRequest.class.isInstance(svRequest)
				&& HttpServletResponse.class.isInstance(svResponse)) {
			HttpServletRequest request = HttpServletRequest.class.cast(svRequest);
			HttpServletResponse response = HttpServletResponse.class.cast(svResponse);

			// TODO:別window対応
			if (!response.isCommitted() && this.path != null) {
				if (request.getAttribute(CLAZZ) == null) {
					request.setAttribute(CLAZZ, CLAZZ);

					if (FilterUtil.isGetMethod(request.getMethod())) {
						if (this.path.equals(request.getServletPath())) {
							if (!setRapResponse(request, response)) {
								sendNotFound(response);
							}
							return;
						}
					} else if (FilterUtil.isPostMethod(request.getMethod())) {
						if (this.path.equals(request.getServletPath())) {
							sendNotFound(response);
						} else {
							chain.doFilter(request, new DuplicateHeaderWrapper(response));
						}
						return;
					}
				} else {
					// jsp
					if (FilterUtil.isView(request)) {
						if (FilterUtil.isPostMethod(request.getMethod())) {
							if (DuplicateHeaderWrapper.class.isInstance(response)) {
								doFilter(request,
										DuplicateHeaderWrapper.class.cast(response), chain);
								return;
							}
						}
					}
				}
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * レスポンス設定
	 *
	 * @param request リクエスト
	 * @param response レスポンス
	 * @return 設定された場合 true を返す。
	 */
	private boolean setRapResponse(final HttpServletRequest request,
			final HttpServletResponse response) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			synchronized (SessionMutexListener.getMutex(session)) {
				DuplicateBodyResponse dbr = DuplicateBodyResponse.class.cast(
						session.getAttribute(this.path));
				if (dbr != null) {
					try {
						dbr.copyResponse(response);
					} catch (IOException ex) {
						LogManager.getLogger().info(ex.getMessage());
					}
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * NotFound送信
	 * @param response レスポンス
	 */
	private void sendNotFound(final HttpServletResponse response) {
		try {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			response.flushBuffer();
		} catch (IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}

	/**
	 * フィルタ処理
	 * @param request リクエスト
	 * @param dhw レスポンスラッパ
	 * @param chain チェイン
	 * @throws ServletException サーブレット例外
	 * @throws IOException IO例外
	 */
	private void doFilter(final HttpServletRequest request, final DuplicateHeaderWrapper dhw,
			final FilterChain chain) throws ServletException, IOException {

		InterceptResponseWrapper wrap = new InterceptResponseWrapper(
				dhw, dhw.getDuplicateHeaderResponse());

		chain.doFilter(request, wrap);

		HttpServletResponse hsr = dhw.getHttpResponse();
		if (!hsr.isCommitted()) {
			setAttribute(request, wrap.getDuplicateBodyResponse());

			try {
				hsr.reset();
				hsr.resetBuffer();
				hsr.setStatus(HttpServletResponse.SC_SEE_OTHER);
				hsr.setHeader("Location", hsr.encodeRedirectURL(getLocation()));
				hsr.setContentLength(0);
				hsr.flushBuffer();
			} catch (IOException ex) {
				LogManager.getLogger().info(ex.getMessage());
			}
		}
	}

	/**
	 * セション設定
	 *
	 * @param request リクエスト
	 * @param dbr DuplicateBodyResponse
	 */
	private void setAttribute(final HttpServletRequest request, final DuplicateBodyResponse dbr) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			synchronized (SessionMutexListener.getMutex(session)) {
				session.setAttribute(this.path, dbr);
			}
		}
	}

	/**
	 * Location取得
	 *
	 * @return Location
	 */
	private String getLocation() {
		if (this.path != null && this.path.startsWith("/")) {
			return this.path.substring(1);
		}
		return this.path;
	}
}
